package com.frostwire.mp3;

public class MpegFrame {
    public static final String MPEG_VERSION_1_0 = "1.0";
    public static final String MPEG_VERSION_2_0 = "2.0";
    public static final String MPEG_VERSION_2_5 = "2.5";
    public static final String MPEG_LAYER_1 = "I";
    public static final String MPEG_LAYER_2 = "II";
    public static final String MPEG_LAYER_3 = "III";
    public static final String[] MPEG_LAYERS = {null, MPEG_LAYER_1, MPEG_LAYER_2, MPEG_LAYER_3};
    public static final String CHANNEL_MODE_MONO = "Mono";
    public static final String CHANNEL_MODE_DUAL_MONO = "Dual mono";
    public static final String CHANNEL_MODE_JOINT_STEREO = "Joint stereo";
    public static final String CHANNEL_MODE_STEREO = "Stereo";
    public static final String MODE_EXTENSION_BANDS_4_31 = "Bands 4-31";
    public static final String MODE_EXTENSION_BANDS_8_31 = "Bands 8-31";
    public static final String MODE_EXTENSION_BANDS_12_31 = "Bands 12-31";
    public static final String MODE_EXTENSION_BANDS_16_31 = "Bands 16-31";
    public static final String MODE_EXTENSION_NONE = "None";
    public static final String MODE_EXTENSION_INTENSITY_STEREO = "Intensity stereo";
    public static final String MODE_EXTENSION_M_S_STEREO = "M/S stereo";
    public static final String MODE_EXTENSION_INTENSITY_M_S_STEREO = "Intensity & M/S stereo";
    public static final String MODE_EXTENSION_NA = "n/a";
    public static final String EMPHASIS_NONE = "None";
    public static final String EMPHASIS__50_15_MS = "50/15 ms";
    public static final String EMPHASIS_CCITT_J_17 = "CCITT J.17";
    private static final int FRAME_DATA_LENGTH = 4;
    private static final int FRAME_SYNC = 0x7FF;
    private static final long BITMASK_FRAME_SYNC = 0xFFE00000L;
    private static final long BITMASK_VERSION = 0x180000L;
    private static final long BITMASK_LAYER = 0x60000L;
    private static final long BITMASK_PROTECTION = 0x10000L;
    private static final long BITMASK_BITRATE = 0xF000L;
    private static final long BITMASK_SAMPLE_RATE = 0xC00L;
    private static final long BITMASK_PADDING = 0x200L;
    private static final long BITMASK_PRIVATE = 0x100L;
    private static final long BITMASK_CHANNEL_MODE = 0xC0L;
    private static final long BITMASK_MODE_EXTENSION = 0x30L;
    private static final long BITMASK_COPYRIGHT = 0x8L;
    private static final long BITMASK_ORIGINAL = 0x4L;
    private static final long BITMASK_EMPHASIS = 0x3L;
    private String version;
    private int layer;
    private boolean protection;
    private int bitrate;
    private int sampleRate;
    private boolean padding;
    private boolean privat;
    private String channelMode;
    private String modeExtension;
    private boolean copyright;
    private boolean original;
    private String emphasis;

    public MpegFrame(byte[] frameData) throws InvalidDataException {
        if (frameData.length < FRAME_DATA_LENGTH) throw new InvalidDataException("Mpeg frame too short");
        long frameHeader = BufferTools.unpackInteger(frameData[0], frameData[1], frameData[2], frameData[3]);
        setFields(frameHeader);
    }

    public MpegFrame(byte frameData1, byte frameData2, byte frameData3, byte frameData4) throws InvalidDataException {
        long frameHeader = BufferTools.unpackInteger(frameData1, frameData2, frameData3, frameData4);
        setFields(frameHeader);
    }

    protected MpegFrame() {
    }

    private void setFields(long frameHeader) throws InvalidDataException {
        long frameSync = extractField(frameHeader, BITMASK_FRAME_SYNC);
        if (frameSync != FRAME_SYNC) throw new InvalidDataException("Frame sync missing");
        setVersion(extractField(frameHeader, BITMASK_VERSION));
        setLayer(extractField(frameHeader, BITMASK_LAYER));
        setProtection(extractField(frameHeader, BITMASK_PROTECTION));
        setBitRate(extractField(frameHeader, BITMASK_BITRATE));
        setSampleRate(extractField(frameHeader, BITMASK_SAMPLE_RATE));
        setPadding(extractField(frameHeader, BITMASK_PADDING));
        setPrivate(extractField(frameHeader, BITMASK_PRIVATE));
        setChannelMode(extractField(frameHeader, BITMASK_CHANNEL_MODE));
        setModeExtension(extractField(frameHeader, BITMASK_MODE_EXTENSION));
        setCopyright(extractField(frameHeader, BITMASK_COPYRIGHT));
        setOriginal(extractField(frameHeader, BITMASK_ORIGINAL));
        setEmphasis(extractField(frameHeader, BITMASK_EMPHASIS));
    }

    protected int extractField(long frameHeader, long bitMask) {
        int shiftBy = 0;
        for (int i = 0; i <= 31; i++) {
            if (((bitMask >> i) & 1) != 0) {
                shiftBy = i;
                break;
            }
        }
        return (int) ((frameHeader >> shiftBy) & (bitMask >> shiftBy));
    }

    private void setBitRate(int bitrate) throws InvalidDataException {
        if (MPEG_VERSION_1_0.equals(version)) {
            if (layer == 1) {
                switch (bitrate) {
                    case 1:
                        this.bitrate = 32;
                        return;
                    case 2:
                        this.bitrate = 64;
                        return;
                    case 3:
                        this.bitrate = 96;
                        return;
                    case 4:
                        this.bitrate = 128;
                        return;
                    case 5:
                        this.bitrate = 160;
                        return;
                    case 6:
                        this.bitrate = 192;
                        return;
                    case 7:
                        this.bitrate = 224;
                        return;
                    case 8:
                        this.bitrate = 256;
                        return;
                    case 9:
                        this.bitrate = 288;
                        return;
                    case 10:
                        this.bitrate = 320;
                        return;
                    case 11:
                        this.bitrate = 352;
                        return;
                    case 12:
                        this.bitrate = 384;
                        return;
                    case 13:
                        this.bitrate = 416;
                        return;
                    case 14:
                        this.bitrate = 448;
                        return;
                }
            } else if (layer == 2) {
                switch (bitrate) {
                    case 1:
                        this.bitrate = 32;
                        return;
                    case 2:
                        this.bitrate = 48;
                        return;
                    case 3:
                        this.bitrate = 56;
                        return;
                    case 4:
                        this.bitrate = 64;
                        return;
                    case 5:
                        this.bitrate = 80;
                        return;
                    case 6:
                        this.bitrate = 96;
                        return;
                    case 7:
                        this.bitrate = 112;
                        return;
                    case 8:
                        this.bitrate = 128;
                        return;
                    case 9:
                        this.bitrate = 160;
                        return;
                    case 10:
                        this.bitrate = 192;
                        return;
                    case 11:
                        this.bitrate = 224;
                        return;
                    case 12:
                        this.bitrate = 256;
                        return;
                    case 13:
                        this.bitrate = 320;
                        return;
                    case 14:
                        this.bitrate = 384;
                        return;
                }
            } else if (layer == 3) {
                switch (bitrate) {
                    case 1:
                        this.bitrate = 32;
                        return;
                    case 2:
                        this.bitrate = 40;
                        return;
                    case 3:
                        this.bitrate = 48;
                        return;
                    case 4:
                        this.bitrate = 56;
                        return;
                    case 5:
                        this.bitrate = 64;
                        return;
                    case 6:
                        this.bitrate = 80;
                        return;
                    case 7:
                        this.bitrate = 96;
                        return;
                    case 8:
                        this.bitrate = 112;
                        return;
                    case 9:
                        this.bitrate = 128;
                        return;
                    case 10:
                        this.bitrate = 160;
                        return;
                    case 11:
                        this.bitrate = 192;
                        return;
                    case 12:
                        this.bitrate = 224;
                        return;
                    case 13:
                        this.bitrate = 256;
                        return;
                    case 14:
                        this.bitrate = 320;
                        return;
                }
            }
        } else if (MPEG_VERSION_2_0.equals(version) || MPEG_VERSION_2_5.equals(version)) {
            if (layer == 1) {
                switch (bitrate) {
                    case 1:
                        this.bitrate = 32;
                        return;
                    case 2:
                        this.bitrate = 48;
                        return;
                    case 3:
                        this.bitrate = 56;
                        return;
                    case 4:
                        this.bitrate = 64;
                        return;
                    case 5:
                        this.bitrate = 80;
                        return;
                    case 6:
                        this.bitrate = 96;
                        return;
                    case 7:
                        this.bitrate = 112;
                        return;
                    case 8:
                        this.bitrate = 128;
                        return;
                    case 9:
                        this.bitrate = 144;
                        return;
                    case 10:
                        this.bitrate = 160;
                        return;
                    case 11:
                        this.bitrate = 176;
                        return;
                    case 12:
                        this.bitrate = 192;
                        return;
                    case 13:
                        this.bitrate = 224;
                        return;
                    case 14:
                        this.bitrate = 256;
                        return;
                }
            } else if (layer == 2 || layer == 3) {
                switch (bitrate) {
                    case 1:
                        this.bitrate = 8;
                        return;
                    case 2:
                        this.bitrate = 16;
                        return;
                    case 3:
                        this.bitrate = 24;
                        return;
                    case 4:
                        this.bitrate = 32;
                        return;
                    case 5:
                        this.bitrate = 40;
                        return;
                    case 6:
                        this.bitrate = 48;
                        return;
                    case 7:
                        this.bitrate = 56;
                        return;
                    case 8:
                        this.bitrate = 64;
                        return;
                    case 9:
                        this.bitrate = 80;
                        return;
                    case 10:
                        this.bitrate = 96;
                        return;
                    case 11:
                        this.bitrate = 112;
                        return;
                    case 12:
                        this.bitrate = 128;
                        return;
                    case 13:
                        this.bitrate = 144;
                        return;
                    case 14:
                        this.bitrate = 160;
                        return;
                }
            }
        }
        throw new InvalidDataException("Invalid bitrate in frame header");
    }

    private void setPadding(int paddingBit) {
        this.padding = (paddingBit == 1);
    }

    public int getBitrate() {
        return bitrate;
    }

    public String getChannelMode() {
        return channelMode;
    }

    private void setChannelMode(int channelMode) throws InvalidDataException {
        switch (channelMode) {
            case 0:
                this.channelMode = CHANNEL_MODE_STEREO;
                break;
            case 1:
                this.channelMode = CHANNEL_MODE_JOINT_STEREO;
                break;
            case 2:
                this.channelMode = CHANNEL_MODE_DUAL_MONO;
                break;
            case 3:
                this.channelMode = CHANNEL_MODE_MONO;
                break;
            default:
                throw new InvalidDataException("Invalid channel mode in frame header");
        }
    }

    public boolean isCopyright() {
        return copyright;
    }

    private void setCopyright(int copyrightBit) {
        this.copyright = (copyrightBit == 1);
    }

    public String getEmphasis() {
        return emphasis;
    }

    private void setEmphasis(int emphasis) throws InvalidDataException {
        switch (emphasis) {
            case 0:
                this.emphasis = EMPHASIS_NONE;
                break;
            case 1:
                this.emphasis = EMPHASIS__50_15_MS;
                break;
            case 3:
                this.emphasis = EMPHASIS_CCITT_J_17;
                break;
            default:
                throw new InvalidDataException("Invalid emphasis in frame header");
        }
    }

    public String getLayer() {
        return MPEG_LAYERS[layer];
    }

    private void setLayer(int layer) throws InvalidDataException {
        switch (layer) {
            case 1:
                this.layer = 3;
                break;
            case 2:
                this.layer = 2;
                break;
            case 3:
                this.layer = 1;
                break;
            default:
                throw new InvalidDataException("Invalid mpeg layer description in frame header");
        }
    }

    public String getModeExtension() {
        return modeExtension;
    }

    private void setModeExtension(int modeExtension) throws InvalidDataException {
        if (!CHANNEL_MODE_JOINT_STEREO.equals(channelMode)) {
            this.modeExtension = MODE_EXTENSION_NA;
        } else {
            if (layer == 1 || layer == 2) {
                switch (modeExtension) {
                    case 0:
                        this.modeExtension = MODE_EXTENSION_BANDS_4_31;
                        return;
                    case 1:
                        this.modeExtension = MODE_EXTENSION_BANDS_8_31;
                        return;
                    case 2:
                        this.modeExtension = MODE_EXTENSION_BANDS_12_31;
                        return;
                    case 3:
                        this.modeExtension = MODE_EXTENSION_BANDS_16_31;
                        return;
                }
            } else if (layer == 3) {
                switch (modeExtension) {
                    case 0:
                        this.modeExtension = MODE_EXTENSION_NONE;
                        return;
                    case 1:
                        this.modeExtension = MODE_EXTENSION_INTENSITY_STEREO;
                        return;
                    case 2:
                        this.modeExtension = MODE_EXTENSION_M_S_STEREO;
                        return;
                    case 3:
                        this.modeExtension = MODE_EXTENSION_INTENSITY_M_S_STEREO;
                        return;
                }
            }
            throw new InvalidDataException("Invalid mode extension in frame header");
        }
    }

    public boolean isOriginal() {
        return original;
    }

    private void setOriginal(int originalBit) {
        this.original = (originalBit == 1);
    }

    public boolean hasPadding() {
        return padding;
    }

    public boolean isPrivate() {
        return privat;
    }

    private void setPrivate(int privateBit) {
        this.privat = (privateBit == 1);
    }

    public boolean isProtection() {
        return protection;
    }

    private void setProtection(int protectionBit) {
        this.protection = (protectionBit == 1);
    }

    public int getSampleRate() {
        return sampleRate;
    }

    private void setSampleRate(int sampleRate) throws InvalidDataException {
        if (MPEG_VERSION_1_0.equals(version)) {
            switch (sampleRate) {
                case 0:
                    this.sampleRate = 44100;
                    return;
                case 1:
                    this.sampleRate = 48000;
                    return;
                case 2:
                    this.sampleRate = 32000;
                    return;
            }
        } else if (MPEG_VERSION_2_0.equals(version)) {
            switch (sampleRate) {
                case 0:
                    this.sampleRate = 22050;
                    return;
                case 1:
                    this.sampleRate = 24000;
                    return;
                case 2:
                    this.sampleRate = 16000;
                    return;
            }
        } else if (MPEG_VERSION_2_5.equals(version)) {
            switch (sampleRate) {
                case 0:
                    this.sampleRate = 11025;
                    return;
                case 1:
                    this.sampleRate = 12000;
                    return;
                case 2:
                    this.sampleRate = 8000;
                    return;
            }
        }
        throw new InvalidDataException("Invalid sample rate in frame header");
    }

    public String getVersion() {
        return version;
    }

    private void setVersion(int version) throws InvalidDataException {
        switch (version) {
            case 0:
                this.version = MPEG_VERSION_2_5;
                break;
            case 2:
                this.version = MPEG_VERSION_2_0;
                break;
            case 3:
                this.version = MPEG_VERSION_1_0;
                break;
            default:
                throw new InvalidDataException("Invalid mpeg audio version in frame header");
        }
    }

    public int getLengthInBytes() {
        long length;
        int pad;
        if (padding) pad = 1;
        else pad = 0;
        if (layer == 1) {
            length = ((48000 * bitrate) / sampleRate) + (pad * 4);
        } else {
            length = ((144000 * bitrate) / sampleRate) + pad;
        }
        return (int) length;
    }
}
